package yeqiang.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import yeqiang.entity.LocationDO;
import yeqiang.service.IP2LocationService;
import yeqiang.util.InetUtils;
import yeqiang.util.JacksonUtil;
import yeqiang.vo.LocationVO;

import java.io.*;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author yeqiang
 * @since 2021/6/2 下午1:47
 */
@Component
@Slf4j
public class IP2LocationServiceImpl implements IP2LocationService {
    /**
     * 原始数据库文件
     */
    private static final String DB_FILE = "/IP2LOCATION-LITE-DB1.CSV";
    /**
     * 正则表达式，匹配原始数据文件国家等信息
     */
    private static final String REGEX_COUNTRY = "\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\",\"([^\"]+)\"";
    /**
     * Redis数据库中固定的zset名称
     */
    private static final String ZSET_IP2LOCATION = "z_ip2location";

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Override
    public void init() {
        try {
            log.info("start to init zset data");
            Pattern pattern = Pattern.compile(REGEX_COUNTRY);
            InputStream inputStream = getClass().getResourceAsStream(DB_FILE);
            log.debug(inputStream.toString());
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                log.debug(line);
                /**
                 * 原始数据参考
                 * "0","16777215","-","-"
                 * "16777216","16777471","AU","Australia"
                 * "16777472","16778239","CN","China"
                 * "16778240","16779263","AU","Australia"
                 * "16779264","16781311","CN","China"
                 * "16781312","16785407","JP","Japan"
                 * "16785408","16793599","CN","China"
                 * "16793600","16809983","JP","Japan"
                 * "16809984","16842751","TH","Thailand"
                 */
                Matcher matcher = pattern.matcher(line);
                if (matcher.matches()) {
                    LocationDO locationDO = new LocationDO();
                    locationDO.setIp_start(Long.parseLong(matcher.group(1)));
                    locationDO.setIp_end(Long.parseLong(matcher.group(2)));
                    locationDO.setCountry_abbrevation(matcher.group(3));
                    locationDO.setCountry(matcher.group(4));
                    String jsonString = JacksonUtil.toJsonString(locationDO);
                    log.debug(jsonString);
                    boolean r = redisTemplate.opsForZSet().add(ZSET_IP2LOCATION, jsonString, locationDO.getIp_end());
                    log.debug("result:{}", r);
                } else {
                    log.warn("{}, cannot match pattern:{}", line, REGEX_COUNTRY);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public LocationVO ip2location(String ip) {
        long intIP = InetUtils.ip2long(ip);
        log.debug("convert {}->{}", ip, intIP);
        // 将输入ip作为score查询Redis ZSET
        Set<String> resutlSet = redisTemplate.opsForZSet().rangeByScore(ZSET_IP2LOCATION, intIP, 4294967295.0, 0, 1);
        if (resutlSet.size() == 1) {
            // 命中一条记录
            String jsonString = resutlSet.iterator().next();
            log.debug("match: {}", jsonString);
            try {
                LocationDO locationDO = (LocationDO) JacksonUtil.parse(jsonString, LocationDO.class);
                if (locationDO.getIp_start() <= intIP && intIP <= locationDO.getIp_end()) {
                    log.debug("check range ok");
                    LocationVO locationVO = new LocationVO();
                    BeanUtils.copyProperties(locationDO, locationVO);
                    return locationVO;
                } else {
                    log.debug("check range fail");
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return null;
    }
}
